package com.github.blanexie.nexusj.infrastructure.repository.impl

import com.github.blanexie.nexusj.domain.peer.PeerAggregate
import com.github.blanexie.nexusj.domain.peer.PeerId
import com.github.blanexie.nexusj.domain.peer.Size
import com.github.blanexie.nexusj.domain.peer.UploadHost
import com.github.blanexie.nexusj.infrastructure.repository.PeerRepository
import com.github.blanexie.nexusj.support.database
import org.ktorm.database.Database
import org.ktorm.dsl.*
import org.ktorm.entity.*
import org.ktorm.schema.*
import java.time.LocalDateTime


class PeerRepositoryImpl : PeerRepository {

    override fun save(peerAggregate: PeerAggregate) {
        val peerId = peerAggregate.peerId

        val peerDO = PeerDO()
        peerId.id?.let { peerDO.id = it }
        peerDO.peerId = peerId.peerId
        peerDO.authKey = peerId.authKey.authKey
        peerDO.compact = peerAggregate.uploadHost.compact
        peerDO.createTime = peerAggregate.createTime
        peerDO.downloaded = peerAggregate.downloadedBytes.bytes
        peerDO.event = peerAggregate.event
        peerDO.infoHash = peerId.authKey.infoHash
        peerDO.ip = peerAggregate.uploadHost.ip
        peerDO.ipv6 = peerAggregate.uploadHost.ipv6
        peerDO.port = peerAggregate.uploadHost.port
        peerDO.left = peerAggregate.leftBytes.bytes
        peerDO.numwant = peerAggregate.numwant
        peerDO.status = peerAggregate.status
        peerDO.trackerid = peerId.trackerid
        peerDO.uploadedTime = peerAggregate.uploadedTime
        peerDO.uploaded = peerAggregate.uploadedBytes.bytes
        peerDO.userId = peerId.authKey.userId

        database().peerDO.add(peerDO)
    }

    override fun findByPeerId(peerId: PeerId): PeerAggregate? {
        val peerDO = database().peerDO
            .filter {
                (Peer.authKey eq peerId.authKey.authKey) and (Peer.infoHash eq peerId.authKey.infoHash) and (Peer.peerId eq peerId.peerId) and (Peer.status eq 0)
            }
            .lastOrNull()
            ?: return null

        peerId.id = peerDO.id

        return PeerAggregate(
            peerId,
            Size(peerDO.uploaded),
            Size(peerDO.downloaded),
            Size(peerDO.left),
            UploadHost(peerDO.compact, peerDO.ip, peerDO.ipv6, peerDO.port),
            peerDO.event,
            peerDO.numwant,
            peerDO.status,
            peerDO.createTime,
            peerDO.uploadedTime,
        )
    }

    override fun findByInfoHash(infoHash: String): List<PeerDO> {
        return database().peerDO
            .filter {
                (Peer.infoHash eq infoHash) and (Peer.status eq 0)
            }.toList()
    }

}

val Database.peerDO get() = this.sequenceOf(Peer)

interface PeerDO : Entity<PeerDO> {
    companion object : Entity.Factory<PeerDO>()

    var id: Int
    var infoHash: String
    var peerId: String
    var port: Int
    var uploaded: Long
    var downloaded: Long
    var left: Long
    var compact: Boolean
    var event: String
    var ip: String
    var ipv6: String
    var numwant: Int
    var trackerid: String
    var authKey: String
    var userId: Int

    var createTime: LocalDateTime
    var uploadedTime: LocalDateTime
    var status: Int
}


object Peer : Table<PeerDO>("peer") {
    var id = int("id").primaryKey().bindTo { it.id }
    var infoHash = varchar("info_hash").bindTo { it.infoHash }
    var peerId = varchar("peer_id").bindTo { it.peerId }
    var port = int("port").bindTo { it.port }
    var uploaded = long("uploaded").bindTo { it.uploaded }
    var downloaded = long("downloaded").bindTo { it.downloaded }
    var left = long("left").bindTo { it.left }
    var compact = boolean("compact").bindTo { it.compact }
    var event = varchar("event").bindTo { it.event }
    var ip = varchar("ip").bindTo { it.ip }
    var ipv6 = varchar("ipv6").bindTo { it.ipv6 }
    var numwant = int("numwant").bindTo { it.numwant }
    var trackerid = varchar("trackerid").bindTo { it.trackerid }
    var authKey = varchar("auth_key").bindTo { it.authKey }
    var userId = int("user_id").bindTo { it.userId }

    var createTime = datetime("create_time").bindTo { it.createTime }
    var uploadedTime = datetime("uploaded_time").bindTo { it.uploadedTime }
    var status = int("status").bindTo { it.status }

}